#include "devices/input.h" // To read from the keyboard
#include "filesys/filesys.h"
#include "filesys/file.h"
#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/vaddr.h" //Just to get PHYS_BASE
#include <string.h> // To know buffer's size
#include "threads/malloc.h"
#include "userprog/process.h"

#ifdef VM
#include <round.h>
#include "vm/page.h"
#include "userprog/pagedir.h"
#endif

static void syscall_handler (struct intr_frame *);
//Lock to synchronize changes in the per-process open-file table
struct lock fd_table;

void
syscall_init (void) 
{
  lock_init(&sys_call_lock);
  lock_init(&fd_table);
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

/* Given a file descriptor FD and a pointer to the 
   per-process open-file table TABLE returns the struct 
   file* associated to FD
 */
static struct file * 
find(int fd, struct list *table){
  struct list_elem *e;
  
  lock_acquire(&fd_table);
  for(e=list_begin(table); e!=list_end(table); e=list_next(e))
  {
    struct ppof *fi = list_entry(e, struct ppof, elem);
    
    if(fi->fd == fd){
      lock_release(&fd_table);
      return fi->filefd;
    }
  }
  lock_release(&fd_table);
  
  return NULL;
}

/* Reads a byte at user virtual address UADDR.
   UADDR must be below PHYS_BASE.
   Returns the byte value if successful, -1 if a segfault
   occurred. */
static int
get_user (const uint8_t *uaddr)
{
  int result;
  asm ("movl $1f, %0; movzbl %1, %0; 1:"
       : "=&a" (result) : "m" (*uaddr));
  return result;
}
 
/* Writes BYTE to user address UDST.
   UDST must be below PHYS_BASE.
   Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte)
{
  int error_code;
  asm ("movl $1f, %0; movb %b2, %1; 1:"
       : "=&a" (error_code), "=m" (*udst) : "q" (byte));
  return error_code != -1;
}


/* Pointer must be below PHYS_BASE */
static void
below_phys_base(void *mypointer){
  if (mypointer >= PHYS_BASE)
    thread_exit();
}

/* Reads a integer at user virtual address UADDR.
   UADDR must be below PHYS_BASE.
   Returns the int value if successful, -1 if a segfault
   occurred. */
static int
get_user_int (const uint32_t *uaddr)
{
  int result;
  asm ("movl $1f, %0; movl %1, %0; 1:"
       : "=&a" (result) : "m" (*uaddr));
  return result;
}

/*This function helps system calls to get their 
  argument located at ESP. */
static uint32_t
argument(uint32_t *esp)
{
  below_phys_base(esp);
  uint32_t r = get_user_int(esp);
  if((int)r == -1)
    thread_exit();
  return r;
}


/* This functions checks if the whole string is valid */
static void
validate_string(char *s_)
{
  char *s = s_;
  if(get_user((const uint8_t *) s) == -1)
    thread_exit();

  below_phys_base(s);

  while(s[0] != '\0'){
    if(get_user((const uint8_t *)++s) == -1)
      thread_exit();
    below_phys_base(s);
  }
}

/* It checks that the range from BUFFER to SIZE is ok to put new values */
static void
validate_range_put(void *buffer, unsigned size)
{
  void *b = buffer;
  int j = 0;
  uint8_t *put = b + j*PGSIZE;
  while( put < (uint8_t *)(b + size - 1)){
    if(!put_user((uint8_t *)put, (uint8_t)0))
      thread_exit();
    j++;
    put = b + j*PGSIZE;
  }
}

/* It checks that the range from BUFFER to SIZE is valid
 */
static void
validate_range_get(void *buffer, unsigned size)
{
  void *b = buffer;
  int j = 0;
  uint8_t *get = b + j*PGSIZE;
  while( get < (uint8_t *)(b + size - 1)){
    if(get_user((const uint8_t *)get) == -1)
      thread_exit();
    j++;
    get = b + j*PGSIZE;
  }
}

//---------------------------------------------------------
// <------System calls implementation starts here--------->
//---------------------------------------------------------

/* 
 Systemcall Exit: Terminates the current user program, returning status 
 to the kernel. If the process's parent waits for it (see below), this is
 the status that will be returned. Conventionally, a status of 0 indicates
 success and nonzero values indicate errors
*/

static void 
exit (uint32_t *esp)
{
  int status = 0; // Recovering the exit status
  below_phys_base(esp+1);
  status = (int) argument(++esp);
    
  thread_current ()->normal_exit = true;
  thread_current ()->exit_code = status;

  thread_exit();
}

/*
 * Syscall Exec:
 */
static tid_t
exec (uint32_t *esp)
{
	below_phys_base(esp+1);
	char *cmd_line = (char *)argument(++esp);
	below_phys_base(cmd_line);
	validate_string(cmd_line);
	return process_execute(cmd_line);
}

/*
 * Syscall wait
 */
static int
wait (uint32_t *esp)
{
	below_phys_base(esp+1);
	tid_t pid = (tid_t) argument(++esp);
	return process_wait(pid);
}

/* System call number 4
   Implements create system call
*/
static bool
create(uint32_t *esp)
{
  bool ret = false; // The boolean we'll return

  // We recover the file name. 1st argument
  char *file = (char *)argument(++esp);

  // We check the pointer from which we read file name is a valid one
  below_phys_base(file);

  // String validation
  validate_string(file);

  // We recover the initial size. 2nd argument
  int initial_size = (int)argument(++esp);

  lock_acquire(&sys_call_lock);
  ret = filesys_create(file, (unsigned)initial_size);
  lock_release(&sys_call_lock);
  
  return ret;
}

/* System call number 5
   Implements remove system call
 */
static bool
remove(uint32_t *esp)
{
   bool r = false;
  // We recover the first argument. The file name.
  char *filename = (char *)argument(++esp);
  
  // We check the pointer from which we read file name is a valid one
  below_phys_base(filename);

  // String validation
  validate_string(filename);

  lock_acquire(&sys_call_lock);
  r = filesys_remove(filename);
  lock_release(&sys_call_lock);

  return r;
}

/* System call number 6
   Implements open system call
 */
static int
open(uint32_t *esp)
{
  struct thread *process = thread_current();
  int *fd = &process->fd;//The file descriptor id

  // We recover the first argument. The file name.
  char *filename = (char *)argument(++esp);
  
  // We check the pointer from which we read file name is a valid one
  below_phys_base(filename);

  // String validation
  validate_string(filename);

  // We actually open the file using filesys_open
  lock_acquire(&sys_call_lock);
  struct file *file = filesys_open(filename);
  lock_release(&sys_call_lock);
  if(file == NULL)
    return -1;

  // Here the new entry in the per-process table
  struct ppof *of = (struct ppof *) malloc(sizeof(struct ppof));

  of->fd = (*fd)++;
  of->filefd = file;

  lock_acquire(&fd_table);
  list_push_front(&process->ppof_table, &of->elem);
  lock_release(&fd_table);

  return *fd - 1;
}

/* System call 7
   Implements filesize system call
   Returns the size in bytes of the file assocaited with 
   the file descriptor FD
 */
static int
filesize (uint32_t *esp)
{
  //File descriptor
  int fd = (int)argument(++esp);
    // Just in case fd is the stdin or stdout it quits
  if(fd == 0 || fd == 1)
    thread_exit();
  
  struct file* myFile = find(fd, &thread_current()->ppof_table);

  int res;

  if(myFile != NULL){
    lock_acquire(&sys_call_lock);
    res = (int)file_length(myFile);
    lock_release(&sys_call_lock);
    return res;
  }

  return 0;
}

/* System call number 8
   Implements read system call
 */
static int
read(uint32_t *esp)
{
  //File descriptor referring to the file we want to read from
  int fd = (int)argument(++esp);

  if(fd == 1)
    return -1;

  // We recover the buffer where we'll write to.
  void *buffer = (void *)argument(++esp);

  //We check the address buffer is pointing to is < PHYS_BASE
  below_phys_base(buffer);
  
  // Here we get the the size to be read from
  unsigned size = (unsigned)argument(++esp);
  
  // The validation of the range is made here
  validate_range_put(buffer, size);

  if(fd == 0)
  { //We have to read from the keyboard using input_getc()
    unsigned i = 0;
    while(i < size)
    {
      *(uint8_t *)buffer = input_getc();
      lock_acquire(&sys_call_lock);
      buffer = (uint8_t *)buffer + 1;
      lock_release(&sys_call_lock);
      i++;
    }
    return i;
  } 
  else
  {
    struct file* myFile = find(fd, &thread_current()->ppof_table);
    
    if(myFile != NULL)
    {
      int r;
      lock_acquire(&sys_call_lock);
      r = (int)file_read(myFile, buffer, size);
      lock_release(&sys_call_lock);
      return r;
    }
  }
  return -1;
}

/* System call number 9
   Implements write system call
 */
static int
write(uint32_t *esp)
{
  // We recover the file descriptor
  int fd = (int)argument(++esp);

  if(fd == 0)
    return 0;

  // We recover the buffer from which we'll print out characters.
  void *buffer = (void *)argument(++esp);

  // Buffer must be below PHYS_BASE
  below_phys_base(buffer);
 
  // Here we get the the size to be read from
  unsigned size = (unsigned)argument(++esp);

  //We validate the range here
  validate_range_get(buffer, size);

  // We check that buffer points to a valid address. We just read the buffer from memory
  //if(below_phys_base(buffer) && get_user((const uint8_t*)buffer) != -1) {
  if(fd == 1)
  {
    lock_acquire(&sys_call_lock);
    putbuf(buffer, size);
    lock_release(&sys_call_lock);
    return (int)size;
  } 
  else
  {//General file descriptor implementation goes here
    struct file* myFile = find(fd, &thread_current()->ppof_table);
    int res;
    if(myFile != NULL)
    {
      lock_acquire(&sys_call_lock);
      res = (int)file_write(myFile, buffer, size);
      lock_release(&sys_call_lock);
      return res;
    }
  }
  
  return 0;
}

/* System call number 10
   Implements seek system call */
 
static void
seek(uint32_t *esp)
{
  // We recover the file descriptor
  int fd = (int)argument(++esp);
  
  // Just in case the stdin or stdout is passed
  if(fd == 0 || fd == 1)
    thread_exit();
  
  // We recover the position we want to cahnge
  unsigned position = (unsigned)argument(++esp);
  
  struct file* myFile = find(fd, &thread_current()->ppof_table);
  
  if(myFile != NULL)
  { 
    lock_acquire(&sys_call_lock);
    file_seek(myFile, position);
    lock_release(&sys_call_lock);
  }
  else
  {
    thread_exit();
  }
}

/* System call number 11
   Implements tell system call 
 */
static unsigned
tell(uint32_t *esp)
{
  // We recover the file descriptor
  int fd = (int)argument(++esp);
  
  // Just in case the stdin or stdout is passed
  if(fd == 0 || fd == 1)
    thread_exit();

  struct file* myFile = find(fd, &thread_current()->ppof_table);
  
  unsigned res;
  if(myFile != NULL)
  { 
    lock_acquire(&sys_call_lock);
    res = (unsigned)file_tell(myFile);
    lock_release(&sys_call_lock);
    return res;
  }
  
  return 0;
}

/* System call number 12
   Implements system call close
 */
static void
close(uint32_t *esp)
{
  // We recover the file descriptor
  int fd = (int)argument(++esp);

  // Just in case fd is the stdin or stdout it quits
  if(fd == 0 || fd == 1)
    thread_exit();

  struct list *table = &thread_current()->ppof_table;
  
  struct file* myFile = find(fd, table);
  
  if(myFile != NULL)
  { 
    lock_acquire(&sys_call_lock);
    file_close(myFile);
    lock_release(&sys_call_lock);
    
    // We remove the per-process open-file entry with file descriptor FD
    struct list_elem *e;
    struct ppof *entrada;
    
    lock_acquire(&fd_table);
    for(e=list_begin(table); e!=list_end(table); e=list_next(e))
    {
      entrada = list_entry(e, struct ppof, elem);
      if(entrada->fd == fd)
      {
        list_remove(e);
        free(entrada);
        break;
      }	
    }
    lock_release(&fd_table);
  } 
  else
  {
    thread_exit();
  }
}

#ifdef VM
/* Syscalls relative to mapped files into memory */
static int
handle_mmap (uint32_t *esp) 
{ 
  uint32_t fd;
  void *addr;
  struct file* myFile;
  uint32_t flen;
  void *last_addr;
  struct list_elem *e;
  struct mapped_file *mf;
  struct thread *cur;
  struct page *p;
  
  cur = thread_current ();
  
  // Recovering the fd
  below_phys_base(esp+1);
  fd = (int) argument(++esp);
  
  // Check if the user tries to map the console or
  // the keyboard
  if(fd==0 || fd==1)
    return -1;
  
  // Searching the specified file
  myFile = find(fd, &cur->ppof_table);
  
  // if file is not present, fails
  if(myFile==NULL)
    return -1;
  
  // if file has a length of 0 bytes the file, fails
  flen = file_length (myFile);
  if(flen == 0)
    return -1;
  
  // Recovering address  
  below_phys_base(esp+1);
  addr = (void *) argument(++esp);
  
  // if address is zero, fails
  if((uint32_t)addr == 0)
    return -1;
  
  // If address isn't page aligned, fails
  if(((uint32_t)addr % PGSIZE) != 0)
    return -1;
    
  // Check that the file doesn't overlap any other
  // mapped file, or the stack or data or code segment
  // round if file size is not page aligned
  last_addr = (void *) ROUND_UP ((uint32_t)addr + (uint32_t)flen, (uint32_t)PGSIZE);
  
  // Check if the file is going to overlap the stack
  if((uint32_t)last_addr>(uint32_t)cur->min_stack_addrs)
    return -1;
    
  // Check if the file is going to overlap the code or data segment
  if((uint32_t)addr < (uint32_t)cur->max_cds_addr)
    return -1;
    
  // Check if this file is going to overlap othe file yet mapped.
  for(e=list_begin (&cur->mf_table); e!=list_end (&cur->mf_table); e=list_next (e))
  {
    mf = list_entry  (e, struct mapped_file, elem);
    
    // Check that no mapped file is going to containt this file
    if((uint32_t)addr>=(uint32_t)mf->addr && 
       (uint32_t)addr<((uint32_t)mf->addr + mf->size))
       return -1;
    
    if((uint32_t)last_addr>=(uint32_t)mf->addr && 
       (uint32_t)last_addr<((uint32_t)mf->addr + mf->size))
       return -1;
    
    // Check that this file is not goint to contain oder mapped file
    if((uint32_t)addr<(uint32_t)mf->addr &&
       (uint32_t)last_addr>((uint32_t)mf->addr + mf->size))
       return -1;  
  }
  
  // if we have arrive here, that means everything is ok and
  // we have to map the file in memory
  mf = (struct mapped_file *) malloc (sizeof(struct mapped_file));
  if(mf==NULL)
  {
    printf ("What the fuck mapping a file?\n");
    thread_exit ();
  }
  mf->addr = addr;
  mf->size = file_length (myFile);
  mf->file = file_reopen (myFile);
  mf->mapid = cur->mmid;
  
  uint32_t lps = 0;
  
  while (flen > 0)
  {
    p = (struct page *) malloc (sizeof(struct page));
    p->read_b = (flen<PGSIZE) ? flen : PGSIZE;
    p->zero_b = PGSIZE - flen;
    p->location = mf->file;
    p->pg = PAG_FILE;
    p->writable = true;
    p->page = addr;
    p->ofs = (lps++) * PGSIZE;
    
    lock_acquire (&cur->lock_st);
    list_push_front (&cur->supplemental_list, &p->elem);
    lock_release (&cur->lock_st);
    
    flen -= p->read_b;
    addr = (void *)((uint32_t)addr + (uint32_t)PGSIZE);
  }
  
  // TODO: synchronization?
  list_push_front (&cur->mf_table, &mf->elem);
  
  return cur->mmid++;
}

/*  */
static struct mapped_file *
find_mapped_file (uint32_t mapping)
{
  struct list_elem *e;
  struct thread *cur;
  struct mapped_file *mf;
  
  cur = thread_current ();
  
  for(e=list_begin (&cur->mf_table); e!=list_end (&cur->mf_table); e=list_next (e)) 
  {
    mf = list_entry (e, struct mapped_file, elem);
    if(mf->mapid == mapping)
      return mf;
  }
  
  return NULL;
}

/*  */
static void
handle_munmap (uint32_t *esp) 
{
  uint32_t mapping;
  struct thread *cur;
  struct mapped_file *mf;
  int write_b;
  struct page *p;
  
  cur = thread_current ();
  
  // Recovering the mapping id
  below_phys_base(esp+1);
  mapping = (int) argument(++esp);
 
  // Finds the mapped file
  mf = find_mapped_file (mapping);
  
  if(mf!=NULL) 
  {
    uint32_t size = mf->size;
    void * addr = mf->addr;
    void *kpage;
    
    while (size > 0)
    {
      p = supplemental_table_look_up (addr);
      if(p == NULL)
      {
        printf ("WTF unmap?\n");
        thread_exit ();
      }
      
      write_b = p->read_b;
        
      lock_acquire (&thread_current ()->lock_st);
      
      kpage = pagedir_get_page (thread_current ()->pagedir, p->page);
      
      if(kpage != NULL)
      {
        if(pagedir_is_dirty (thread_current ()->pagedir, p->page))
        {
          if(file_write_at (mf->file, kpage, write_b, p->ofs) != write_b)
          {
            printf ("WTF unmap? writing\n");
            thread_exit ();
          }
        }
        
        pagedir_clear_page (cur->pagedir, mf->addr);
      }
      
      lock_release (&thread_current ()->lock_st);
      
      supplemental_table_free_page (p);
      
      size -= write_b;
      addr += write_b;
    }
    
    file_close (mf->file);
    
    // TODO: synchronization?
    list_remove (&mf->elem);
    
    free (mf);
  }
}
#endif

static int
handle_mkdir (uint32_t *esp)
{
  char *name;
  int ret;
  
  below_phys_base(esp+1);
  name = (char *) argument(++esp);
  
  if(strcmp(name, "")==0)
    return 0;
    
  lock_acquire(&sys_call_lock);
  ret = filesys_create(name, 1);
  lock_release(&sys_call_lock);
    
  return ret;
}

static void
syscall_handler (struct intr_frame *f) 
{
  int tipo = 0;
  // The system call number is in the 32-bit word at the caller's stack pointer
  uint32_t *esp = (uint32_t*)f->esp;

  below_phys_base((void *) esp);
  tipo = (int)argument(esp);
  
  switch(tipo)
  {
    case SYS_HALT: // Halt system call

      break;
    case SYS_EXIT: // Exits system call
      exit (esp);
      break;
    case SYS_EXEC: // Exec system call
      f->eax = (uint32_t) exec(f->esp);
      break;
    case SYS_WAIT: // Wait system call
    	f->eax = wait (esp);
      break;
    case SYS_CREATE: // Create system call
      f -> eax = create(esp);
      break;
    case SYS_REMOVE: // Remove system call
      f -> eax = remove(esp);
      break;
    case SYS_OPEN: // Open system call
      f -> eax = open(esp);
      break;
    case SYS_FILESIZE: // Filesize system call
      f -> eax = filesize(esp);
      break;
    case SYS_READ: // Read system call
      f -> eax = read(esp);
      break;
    case SYS_WRITE: // Write system call
      f -> eax = write(esp);
      break;
    case SYS_SEEK: // Seek system call
      seek(esp);
      break;
    case SYS_TELL: // Tell system call
      f -> eax = tell(esp);
      break;
    case SYS_CLOSE: // Close system call
      close(esp);
      break;
    case SYS_MMAP:
      #ifdef VM
      f -> eax = handle_mmap (esp);
      #endif
      break;
    case SYS_MUNMAP:
      #ifdef VM
      handle_munmap (esp);
      #endif
      break;
    case SYS_CHDIR:
      f -> eax = true;
      break;
    case SYS_MKDIR:
      f -> eax = handle_mkdir(esp);
      break;
    case SYS_READDIR:
      break;
    case SYS_ISDIR:
      f -> eax = true;
      break;
    case SYS_INUMBER:
      break;
    default : 
      printf("System call not implemented yet");
      thread_exit ();
      break;
  }
}


